Move XendDomainInfo.{create,recreate,parseConfig} to the top level of the
authoremellor@ewan <emellor@ewan>
Tue, 4 Oct 2005 01:21:28 +0000 (02:21 +0100)
committeremellor@ewan <emellor@ewan>
Tue, 4 Oct 2005 01:21:28 +0000 (02:21 +0100)
domain.  This allows us to refer to them using an import statement, rather than
a from .. import.  This is a step towards getting rid of the xroot hack.  All
other references to XendDomainInfo methods need to be doubly qualified (once
for the module, once for the class).

Remove XendDomainDict, replacing it with a simple dictionary, folding the
get_by_name method into XendDomain.

Replace XendDomain.refresh_lock with a domains_lock which goes around any
reference to XendDomain.domains or anything that will create or destroy a
domain.  This serialises most accesses through XendDomain, ensuring that we will
not return stale state when racing against the watches fired in separate
threads.  This should have fixed bugs #270 and #234.

Added a number of domain_get_xyz methods.  Those named xyz_nr are to allow
components internal to XendDomain (XendDomainInfo, XendCheckpoint) to call back
into XendDomain without triggering further calls to XendDomain.refresh.  The
other methods simply make it clear which fallback behaviour is expected.

Replace XendDomainInfo.domain_exists with XendDomainInfo.domain_by_name; the
internals of this method needed to change to match those changes above, and it
has been a misnomer for some time.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
tools/python/xen/xend/XendDomain.py
tools/python/xen/xend/XendDomainInfo.py
tools/python/xen/xend/server/DevController.py
tools/python/xen/xend/server/SrvDomainDir.py

index 9e13e8735ebb771506ceb57f70c17ee810593fa4..f7da646e40778fd672c9b5e249d40e374e215acc 100644 (file)
  Needs to be persistent for one uptime.
 """
 import os
+import string
 import threading
 
 import xen.lowlevel.xc
 
+import XendDomainInfo
+
 from xen.xend import sxp
 from xen.xend import XendRoot
 from xen.xend import XendCheckpoint
-from xen.xend.XendDomainInfo import XendDomainInfo
 from xen.xend import EventServer
 from xen.xend.XendError import XendError
 from xen.xend.XendLogging import log
@@ -45,21 +47,10 @@ __all__ = [ "XendDomain" ]
 
 PRIV_DOMAIN = 0
 
-class XendDomainDict(dict):
-    def get_by_name(self, name):
-        try:
-            return filter(lambda d: d.getName() == name, self.values())[0]
-        except IndexError, err:
-            return None
-
 class XendDomain:
     """Index of all domains. Singleton.
     """
 
-    """Dict of domain info indexed by domain id."""
-    domains = None
-
-
     ## public:
     
     def __init__(self):
@@ -68,19 +59,30 @@ class XendDomain:
         # to import XendDomain from XendDomainInfo causes unbounded recursion.
         # So we stuff the XendDomain instance (self) into xroot's components.
         xroot.add_component("xen.xend.XendDomain", self)
-        self.domains = XendDomainDict()
-        self.refresh_lock = threading.Condition()
+        self.domains = {}
+        self.domains_lock = threading.Condition()
         self.watchReleaseDomain()
-        self.refresh()
-        self.dom0_setup()
+
+        self.domains_lock.acquire()
+        try:
+            self.refresh()
+            self.dom0_setup()
+        finally:
+            self.domains_lock.release()
+
 
     def list(self):
         """Get list of domain objects.
 
         @return: domain objects
         """
-        self.refresh()
-        return self.domains.values()
+        self.domains_lock.acquire()
+        try:
+            self.refresh()
+            return self.domains.values()
+        finally:
+            self.domains_lock.release()
+
 
     def list_sorted(self):
         """Get list of domain objects, sorted by name.
@@ -103,7 +105,12 @@ class XendDomain:
     ## private:
 
     def onReleaseDomain(self):
-        self.refresh()
+        self.domains_lock.acquire()
+        try:
+            self.refresh()
+        finally:
+            self.domains_lock.release()
+            
 
     def watchReleaseDomain(self):
         from xen.xend.xenstore.xswatch import xswatch
@@ -133,16 +140,8 @@ class XendDomain:
         return dominfo
 
 
-    def recreate_domain(self, xeninfo):
-        """Refresh initial domain info from db."""
-
-        dominfo = XendDomainInfo.recreate(xeninfo)
-        self._add_domain(dominfo)
-        return dominfo
-
-
     def dom0_setup(self):
-        dom0 = self.domain_lookup(PRIV_DOMAIN)
+        dom0 = self.domains[PRIV_DOMAIN]
         dom0.dom0_enforce_vcpus()
 
 
@@ -179,50 +178,33 @@ class XendDomain:
     def refresh(self):
         """Refresh domain list from Xen.
         """
-        self.refresh_lock.acquire()
-        try:
-            doms = self.xen_domains()
-            for d in self.domains.values():
-                info = doms.get(d.getDomid())
-                if info:
-                    d.update(info)
-                else:
-                    self._delete_domain(d.getDomid())
-            for d in doms:
-                if d not in self.domains and not doms[d]['dying']:
-                    try:
-                        self.recreate_domain(doms[d])
-                    except:
-                        if d == PRIV_DOMAIN:
-                            log.exception(
-                                "Failed to recreate information for domain "
-                                "%d.  Doing nothing except crossing my "
-                                "fingers.", d)
-                        else:
-                            log.exception(
-                                "Failed to recreate information for domain "
-                                "%d.  Destroying it in the hope of "
-                                "recovery.", d)
-                            try:
-                                xc.domain_destroy(dom = d)
-                            except:
-                                log.exception('Destruction of %d failed.', d)
-        finally:
-            self.refresh_lock.release()
-
-
-    def update_domain(self, id):
-        """Update information for a single domain.
-
-        @param id: domain id
-        """
-        dominfo = self.xen_domain(id)
-        if dominfo:
-            d = self.domains.get(id)
-            if d:
-                d.update(dominfo)
-        else:
-            self._delete_domain(id)
+        doms = self.xen_domains()
+        for d in self.domains.values():
+            info = doms.get(d.getDomid())
+            if info:
+                d.update(info)
+            else:
+                self._delete_domain(d.getDomid())
+        for d in doms:
+            if d not in self.domains and not doms[d]['dying']:
+                try:
+                    dominfo = XendDomainInfo.recreate(doms[d])
+                    self._add_domain(dominfo)
+                except:
+                    if d == PRIV_DOMAIN:
+                        log.exception(
+                            "Failed to recreate information for domain "
+                            "%d.  Doing nothing except crossing my "
+                            "fingers.", d)
+                    else:
+                        log.exception(
+                            "Failed to recreate information for domain "
+                            "%d.  Destroying it in the hope of "
+                            "recovery.", d)
+                        try:
+                            xc.domain_destroy(dom = d)
+                        except:
+                            log.exception('Destruction of %d failed.', d)
 
 
     ## public:
@@ -233,9 +215,14 @@ class XendDomain:
         @param config: configuration
         @return: domain
         """
-        dominfo = XendDomainInfo.create(config)
-        self._add_domain(dominfo)
-        return dominfo
+        self.domains_lock.acquire()
+        try:
+            dominfo = XendDomainInfo.create(config)
+            self._add_domain(dominfo)
+            return dominfo
+        finally:
+            self.domains_lock.release()
+
 
     def domain_configure(self, config):
         """Configure an existing domain.
@@ -260,33 +247,72 @@ class XendDomain:
             raise XendError("can't read guest state file %s: %s" %
                             (src, ex[1]))
 
-    def domain_get(self, id):
-        """Get up-to-date info about a domain.
 
-        @param id: domain id
-        @return: domain object (or None)
-        """
-        self.update_domain(id)
-        return self.domains.get(id)
+    def domain_lookup(self, id):
+        self.domains_lock.acquire()
+        try:
+            self.refresh()
+            return self.domains.get(id)
+        finally:
+            self.domains_lock.release()
 
 
-    def domain_lookup(self, id):
-        self.refresh()
-        return self.domains.get(id)
+    def domain_lookup_nr(self, id):
+        self.domains_lock.acquire()
+        try:
+            return self.domains.get(id)
+        finally:
+            self.domains_lock.release()
 
-    def domain_lookup_by_name(self, name):
-        dominfo = self.domains.get_by_name(name)
-        if not dominfo:
-            try:
-                id = int(name)
-                dominfo = self.domain_lookup(id)
-            except ValueError:
-                pass
-        return dominfo
+
+    def domain_lookup_by_name_or_id(self, name):
+        self.domains_lock.acquire()
+        try:
+            self.refresh()
+            return self.domain_lookup_by_name_or_id_nr(name)
+        finally:
+            self.domains_lock.release()
+
+
+    def domain_lookup_by_name_or_id_nr(self, name):
+        self.domains_lock.acquire()
+        try:
+            dominfo = self.domain_lookup_by_name_nr(name)
+
+            if dominfo:
+                return dominfo
+            else:
+                try:
+                    return self.domains.get(int(name))
+                except ValueError:
+                    return None
+        finally:
+            self.domains_lock.release()
+
+
+    def domain_lookup_by_name_nr(self, name):
+        self.domains_lock.acquire()
+        try:
+            matching = filter(lambda d: d.getName() == name,
+                              self.domains.values())
+            n = len(matching)
+            if n == 1:
+                return matching[0]
+            elif n > 1:
+                raise XendError(
+                    'Name uniqueness has been violated for name %s' % name)
+            else:
+                return None
+        finally:
+            self.domains_lock.release()
 
 
     def privilegedDomain(self):
-        return self.domains[PRIV_DOMAIN]
+        self.domains_lock.acquire()
+        try:
+            return self.domains[PRIV_DOMAIN]
+        finally:
+            self.domains_lock.release()
 
  
     def domain_unpause(self, id):
@@ -321,12 +347,13 @@ class XendDomain:
 
         @param reason: shutdown reason: poweroff, reboot, suspend, halt
         """
-        self.callInfo(domid, XendDomainInfo.shutdown, reason)
+        self.callInfo(domid, XendDomainInfo.XendDomainInfo.shutdown, reason)
 
 
     def domain_sysrq(self, domid, key):
         """Send a SysRq to the specified domain."""
-        return self.callInfo(domid, XendDomainInfo.send_sysrq, key)
+        return self.callInfo(domid, XendDomainInfo.XendDomainInfo.send_sysrq,
+                             key)
 
 
     def domain_destroy(self, domid):
@@ -449,32 +476,39 @@ class XendDomain:
     def domain_device_create(self, domid, devconfig):
         """Create a new device for the specified domain.
         """
-        return self.callInfo(domid, XendDomainInfo.device_create, devconfig)
+        return self.callInfo(domid,
+                             XendDomainInfo.XendDomainInfo.device_create,
+                             devconfig)
 
 
     def domain_device_configure(self, domid, devconfig, devid):
         """Configure an existing device in the specified domain.
         @return: updated device configuration
         """
-        return self.callInfo(domid, XendDomainInfo.device_configure,
+        return self.callInfo(domid,
+                             XendDomainInfo.XendDomainInfo.device_configure,
                              devconfig, devid)
 
     
     def domain_device_refresh(self, domid, devtype, devid):
         """Refresh a device."""
-        return self.callInfo(domid, XendDomainInfo.device_refresh, devtype,
-                             devid)
+        return self.callInfo(domid,
+                             XendDomainInfo.XendDomainInfo.device_refresh,
+                             devtype, devid)
 
 
     def domain_device_destroy(self, domid, devtype, devid):
         """Destroy a device."""
-        return self.callInfo(domid, XendDomainInfo.destroyDevice, devtype,
-                             devid)
+        return self.callInfo(domid,
+                             XendDomainInfo.XendDomainInfo.destroyDevice,
+                             devtype, devid)
 
 
     def domain_devtype_ls(self, domid, devtype):
         """Get list of device sxprs for the specified domain."""
-        return self.callInfo(domid, XendDomainInfo.getDeviceSxprs, devtype)
+        return self.callInfo(domid,
+                             XendDomainInfo.XendDomainInfo.getDeviceSxprs,
+                             devtype)
 
 
     def domain_vif_limit_set(self, id, vif, credit, period):
@@ -518,7 +552,8 @@ class XendDomain:
 
         @param mem: memory target (in MiB)
         """
-        self.callInfo(domid, XendDomainInfo.setMemoryTarget, mem << 10)
+        self.callInfo(domid, XendDomainInfo.XendDomainInfo.setMemoryTarget,
+                      mem << 10)
 
 
     def domain_vcpu_hotplug(self, domid, vcpu, state):
@@ -527,12 +562,13 @@ class XendDomain:
         @param vcpu: target VCPU in domain
         @param state: which state VCPU will become
         """
-        self.callInfo(domid, XendDomainInfo.vcpu_hotplug, vcpu, state)
+        self.callInfo(domid, XendDomainInfo.XendDomainInfo.vcpu_hotplug, vcpu,
+                      state)
 
 
     def domain_dumpcore(self, domid):
         """Save a core dump for a crashed domain."""
-        self.callInfo(domid, XendDomainInfo.dumpCore)
+        self.callInfo(domid, XendDomainInfo.XendDomainInfo.dumpCore)
 
 
     ## private:
index 7a479aa698f369c735c50db9cae97821165155b1..45470888560c879bbf347ec8bd1c93b18974c12d 100644 (file)
@@ -120,6 +120,68 @@ ROUNDTRIPPING_CONFIG_ENTRIES = [
     ]
 
 
+def create(config):
+    """Create a VM from a configuration.
+
+    @param config    configuration
+    @raise: VmError for invalid configuration
+    """
+
+    log.debug("XendDomainInfo.create(%s)", config)
+
+    vm = XendDomainInfo(getUuid(), parseConfig(config))
+    vm.construct()
+    vm.refreshShutdown()
+    return vm
+
+
+def recreate(xeninfo):
+    """Create the VM object for an existing domain.  The domain must not
+    be dying, as the paths in the store should already have been removed,
+    and asking us to recreate them causes problems."""
+
+    log.debug("XendDomainInfo.recreate(%s)", xeninfo)
+
+    assert not xeninfo['dying']
+
+    domid = xeninfo['dom']
+    try:
+        dompath = GetDomainPath(domid)
+        if not dompath:
+            raise XendError(
+                'No domain path in store for existing domain %d' % domid)
+        vmpath = xstransact.Read(dompath, "vm")
+        if not vmpath:
+            raise XendError(
+                'No vm path in store for existing domain %d' % domid)
+        uuid = xstransact.Read(vmpath, "uuid")
+        if not uuid:
+            raise XendError(
+                'No vm/uuid path in store for existing domain %d' % domid)
+
+        log.info("Recreating domain %d, UUID %s.", domid, uuid)
+
+        vm = XendDomainInfo(uuid, xeninfo, domid, True)
+
+    except Exception, exn:
+        log.warn(str(exn))
+
+        uuid = getUuid()
+
+        log.info("Recreating domain %d with new UUID %s.", domid, uuid)
+
+        vm = XendDomainInfo(uuid, xeninfo, domid, True)
+        vm.storeVmDetails()
+        vm.storeDomDetails()
+
+    vm.create_channel()
+    if domid == 0:
+        vm.initStoreConnection()
+
+    vm.refreshShutdown(xeninfo)
+    return vm
+
+
 def restore(config):
     """Create a domain and a VM object to do a restore.
 
@@ -134,7 +196,7 @@ def restore(config):
     except TypeError, exn:
         raise VmError('Invalid ssidref in config: %s' % exn)
 
-    vm = XendDomainInfo(uuid, XendDomainInfo.parseConfig(config),
+    vm = XendDomainInfo(uuid, parseConfig(config),
                         xc.domain_create(ssidref = ssidref))
     vm.storeVmDetails()
     vm.configure()
@@ -143,10 +205,87 @@ def restore(config):
     return vm
 
 
-def domain_exists(name):
+def parseConfig(config):
+    def get_cfg(name, conv = None):
+        val = sxp.child_value(config, name)
+
+        if conv and not val is None:
+            try:
+                return conv(val)
+            except TypeError, exn:
+                raise VmError(
+                    'Invalid setting %s = %s in configuration: %s' %
+                    (name, val, str(exn)))
+        else:
+            return val
+
+
+    log.debug("parseConfig: config is %s" % str(config))
+
+    result = {}
+
+    for e in ROUNDTRIPPING_CONFIG_ENTRIES:
+        result[e[0]] = get_cfg(e[0], e[1])
+
+    result['memory']       = get_cfg('memory',     int)
+    result['mem_kb']       = get_cfg('mem_kb',     int)
+    result['maxmem']       = get_cfg('maxmem',     int)
+    result['maxmem_kb']    = get_cfg('maxmem_kb',  int)
+    result['cpu']          = get_cfg('cpu',        int)
+    result['image']        = get_cfg('image')
+
+    try:
+        if result['image']:
+            result['vcpus'] = int(sxp.child_value(result['image'],
+                                                  'vcpus', 1))
+        else:
+            result['vcpus'] = 1
+    except TypeError, exn:
+        raise VmError(
+            'Invalid configuration setting: vcpus = %s: %s' %
+            (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
+
+    result['backend'] = []
+    for c in sxp.children(config, 'backend'):
+        result['backend'].append(sxp.name(sxp.child0(c)))
+
+    result['device'] = []
+    for d in sxp.children(config, 'device'):
+        c = sxp.child0(d)
+        result['device'].append((sxp.name(c), c))
+
+    # Configuration option "restart" is deprecated.  Parse it, but
+    # let on_xyz override it if they are present.
+    restart = get_cfg('restart')
+    if restart:
+        def handle_restart(event, val):
+            if not event in result:
+                result[event] = val
+
+        if restart == "onreboot":
+            handle_restart('on_poweroff', 'destroy')
+            handle_restart('on_reboot',   'restart')
+            handle_restart('on_crash',    'destroy')
+        elif restart == "always":
+            handle_restart('on_poweroff', 'restart')
+            handle_restart('on_reboot',   'restart')
+            handle_restart('on_crash',    'restart')
+        elif restart == "never":
+            handle_restart('on_poweroff', 'destroy')
+            handle_restart('on_reboot',   'destroy')
+            handle_restart('on_crash',    'destroy')
+        else:
+            log.warn("Ignoring malformed and deprecated config option "
+                     "restart = %s", restart)
+
+    log.debug("parseConfig: result is %s" % str(result))
+    return result
+
+
+def domain_by_name(name):
     # See comment in XendDomain constructor.
     xd = get_component('xen.xend.XendDomain')
-    return xd.domain_lookup_by_name(name)
+    return xd.domain_lookup_by_name_nr(name)
 
 def shutdown_reason(code):
     """Get a shutdown reason from a code.
@@ -181,152 +320,6 @@ class XendDomainInfo:
     MINIMUM_RESTART_TIME = 20
 
 
-    def create(cls, config):
-        """Create a VM from a configuration.
-
-        @param config    configuration
-        @raise: VmError for invalid configuration
-        """
-
-        log.debug("XendDomainInfo.create(%s)", config)
-        
-        vm = cls(getUuid(), cls.parseConfig(config))
-        vm.construct()
-        vm.refreshShutdown()
-        return vm
-
-    create = classmethod(create)
-
-
-    def recreate(cls, xeninfo):
-        """Create the VM object for an existing domain.  The domain must not
-        be dying, as the paths in the store should already have been removed,
-        and asking us to recreate them causes problems."""
-
-        log.debug("XendDomainInfo.recreate(%s)", xeninfo)
-
-        assert not xeninfo['dying']
-
-        domid = xeninfo['dom']
-        try:
-            dompath = GetDomainPath(domid)
-            if not dompath:
-                raise XendError(
-                    'No domain path in store for existing domain %d' % domid)
-            vmpath = xstransact.Read(dompath, "vm")
-            if not vmpath:
-                raise XendError(
-                    'No vm path in store for existing domain %d' % domid)
-            uuid = xstransact.Read(vmpath, "uuid")
-            if not uuid:
-                raise XendError(
-                    'No vm/uuid path in store for existing domain %d' % domid)
-
-            log.info("Recreating domain %d, UUID %s.", domid, uuid)
-
-            vm = cls(uuid, xeninfo, domid, True)
-
-        except Exception, exn:
-            log.warn(str(exn))
-
-            uuid = getUuid()
-
-            log.info("Recreating domain %d with new UUID %s.", domid, uuid)
-
-            vm = cls(uuid, xeninfo, domid, True)
-            vm.storeVmDetails()
-            vm.storeDomDetails()
-
-        vm.create_channel()
-        if domid == 0:
-            vm.initStoreConnection()
-
-        vm.refreshShutdown(xeninfo)
-        return vm
-
-    recreate = classmethod(recreate)
-
-
-    def parseConfig(cls, config):
-        def get_cfg(name, conv = None):
-            val = sxp.child_value(config, name)
-
-            if conv and not val is None:
-                try:
-                    return conv(val)
-                except TypeError, exn:
-                    raise VmError(
-                        'Invalid setting %s = %s in configuration: %s' %
-                        (name, val, str(exn)))
-            else:
-                return val
-
-
-        log.debug("parseConfig: config is %s" % str(config))
-
-        result = {}
-
-        for e in ROUNDTRIPPING_CONFIG_ENTRIES:
-            result[e[0]] = get_cfg(e[0], e[1])
-
-        result['memory']       = get_cfg('memory',     int)
-        result['mem_kb']       = get_cfg('mem_kb',     int)
-        result['maxmem']       = get_cfg('maxmem',     int)
-        result['maxmem_kb']    = get_cfg('maxmem_kb',  int)
-        result['cpu']          = get_cfg('cpu',        int)
-        result['image']        = get_cfg('image')
-
-        try:
-            if result['image']:
-                result['vcpus'] = int(sxp.child_value(result['image'],
-                                                      'vcpus', 1))
-            else:
-                result['vcpus'] = 1
-        except TypeError, exn:
-            raise VmError(
-                'Invalid configuration setting: vcpus = %s: %s' %
-                (sxp.child_value(result['image'], 'vcpus', 1), str(exn)))
-
-        result['backend'] = []
-        for c in sxp.children(config, 'backend'):
-            result['backend'].append(sxp.name(sxp.child0(c)))
-
-        result['device'] = []
-        for d in sxp.children(config, 'device'):
-            c = sxp.child0(d)
-            result['device'].append((sxp.name(c), c))
-
-        # Configuration option "restart" is deprecated.  Parse it, but
-        # let on_xyz override it if they are present.
-        restart = get_cfg('restart')
-        if restart:
-            def handle_restart(event, val):
-                if not event in result:
-                    result[event] = val
-
-            if restart == "onreboot":
-                handle_restart('on_poweroff', 'destroy')
-                handle_restart('on_reboot',   'restart')
-                handle_restart('on_crash',    'destroy')
-            elif restart == "always":
-                handle_restart('on_poweroff', 'restart')
-                handle_restart('on_reboot',   'restart')
-                handle_restart('on_crash',    'restart')
-            elif restart == "never":
-                handle_restart('on_poweroff', 'destroy')
-                handle_restart('on_reboot',   'destroy')
-                handle_restart('on_crash',    'destroy')
-            else:
-                log.warn("Ignoring malformed and deprecated config option "
-                         "restart = %s", restart)
-
-        log.debug("parseConfig: result is %s" % str(result))
-        return result
-
-
-    parseConfig = classmethod(parseConfig)
-
-    
     def __init__(self, uuid, info, domid = None, augment = False):
 
         self.uuid = uuid
@@ -957,10 +950,8 @@ class XendDomainInfo:
             if c in '_-.:/+': continue
             if c in string.ascii_letters: continue
             raise VmError('invalid vm name')
-        dominfo = domain_exists(name)
-        # When creating or rebooting, a domain with my name should not exist.
-        # When restoring, a domain with my name will exist, but it should have
-        # my domain id.
+
+        dominfo = domain_by_name(name)
         if not dominfo:
             return
         if dominfo.is_terminated():
index c9fc72ebaaeca0c1ba52db5d2bc347c837fb3450..e93d4051cd5f1e0fb020990756fbc3aef719f7c7 100644 (file)
@@ -193,7 +193,7 @@ class DevController:
 
         backdom_name = sxp.child_value(config, 'backend')
         if backdom_name:
-            backdom = xd.domain_lookup_by_name(backdom_name)
+            backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name)
         else:
             backdom = xd.privilegedDomain()
 
index 430eb592ea1f2ea72354793def953ec02d481a1e..0bbcd8e607c8206de5537978972494e97466f9ca 100644 (file)
@@ -38,7 +38,7 @@ class SrvDomainDir(SrvDir):
         self.xd = XendDomain.instance()
 
     def domain(self, x):
-        dom = self.xd.domain_lookup_by_name(x)
+        dom = self.xd.domain_lookup_by_name_or_id(x)
         if not dom:
             raise XendError('No such domain ' + str(x))
         return SrvDomain(dom)